package org.fujene.memman;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.logging.Logger;

import org.fujene.memcon.ReplicateIndexMemory;
import org.fujene.memcon.IndexDisk;
import org.fujene.structure.Constants;
import org.fujene.structure.ContentIDs;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.ExtendQueue;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.InvertIndexData;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ContentHash;

public class ReplicateMemoryManager{
	// ネットワークを利用する向けのインデックスシステムを分離
	private static final int DIGLENGTH = Constants.SKIPPTR_INTERVAL;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;

	// メモリ・ディスク本体
	private ReplicateIndexMemory memory = null;
	private IndexDisk disk = null;

	public boolean stopFlag = false;

	private int indexcounter = 0;
	private int termcounter = 0;
	private int searchcounter = 0;

	private ContentInfo contentInfo;

	public ReplicateMemoryManager(int blockSize, int blockLines, String dirPath, Logger logger,
			ContentInfo contentInfo){
		this.disk = new IndexDisk(dirPath);
		this.memory = new ReplicateIndexMemory(blockSize, blockLines, disk, contentInfo, logger);
		this.contentInfo = contentInfo;

		logger.fine("Memory manager started.");
	}

	public void storeTerm(String term, long ID, int termfreq, int valueType){
		try{
			// 辞書しまう(あれば追加しない)
			ExtendTerm newDic = storeDictionary(term, valueType);

			// DocIDリストしまう
			storeInvIndex(newDic.ptr, ID, termfreq, valueType);
		}catch(Exception ignored){}
		if(termcounter++ % 100000 == 0) System.gc();
	}

	// 転置インデックスのポインタを取得
	public ExtendTerm searchTerm(String term, int valueType){
		// 単語検索
		ExtendTerm newTerm = lookupTerm(term, valueType);

		if(newTerm.ptr != null)
			return newTerm;
		else return null;
	}

	public ExtendQueue lookupContent(long ID, int priorIndex){
		int i;
		ExtendQueue returnContents = new ExtendQueue(contentInfo.getContentNum(),
				contentInfo.getAppendixNum(), ID);
		LookupPointer priorPtr = null;
		for(i = 0; i < contentInfo.getContentNum(); i++){
			ContentIDs ptrInfo = lookupContentPointer(ID, i);

			if(ptrInfo.currentPtr != null){
				if(i == priorIndex) priorPtr = ptrInfo.currentPtr;
				String content = memory.lookupContents(ptrInfo.currentPtr);
				if(!content.equals("$$$ DELETED"))
					returnContents.contents[i] = content;
				else return Constants.EMPTY_QUEUE;
			}else return null;
		}

		if(priorPtr != null){
			LookupPointer[] supplementPointers = memory.lookupPureContentPointer(priorPtr);
			for(i = 0; i < supplementPointers.length; i++)
				returnContents.appendices[i] = memory.lookupPureContent(supplementPointers[i]);
		}

		return returnContents;
	}

	public ArrayList<ExtendTerm> getTermList(long hashFrom, long hashTo, int valueType){
		ArrayList<ExtendTerm> terms = new ArrayList<ExtendTerm>();
		System.out.println("Get from " + hashFrom + " to " + hashTo);

		// スキップポインタからスタート
		LookupPointer firstPtr = memory.lookupSkipPointer(0);
		LookupPointer nextPtr = firstPtr;
		ExtendTerm queue;
		long hashCode;

		while(nextPtr != null){
			firstPtr = nextPtr;
			queue = memory.lookupDictionary(firstPtr, valueType);
			hashCode = ContentHash.getTermHash(queue.term);
			if(ContentHash.compareRange(hashCode, hashFrom, hashTo)) terms.add(queue);
			nextPtr = memory.lookupDictionaryNext(firstPtr);
		}

		return terms;
	}

	public ArrayList<ExtendQueue> getContentList(long hashFrom, long hashTo, int priorIndex){
		ArrayList<ExtendQueue> terms = new ArrayList<ExtendQueue>();
		int i;
		long hash = 0;
		LookupPointer[] firstPtrs = new LookupPointer[contentInfo.getContentNum()];
		LookupPointer[] nextPtrs = new LookupPointer[contentInfo.getContentNum()];
		LookupPointer[] supplementPtrs = null;
		IndexQueue queue;
		ExtendQueue newTerm;

		for(i = 0; i < contentInfo.getContentNum(); i++){
			// スキップポインタからスタート
			firstPtrs[i] = memory.lookupContentsSkipPointer(0, i);
			nextPtrs[i] = firstPtrs[i];
		}

		while(nextPtrs[0] != null){
			newTerm = new ExtendQueue(contentInfo.getContentNum(), contentInfo.getAppendixNum(), -1);
			for(i = 0; i < contentInfo.getContentNum(); i++){
				if(nextPtrs[i] == null) break;
				firstPtrs[i] = nextPtrs[i];
				queue = memory.lookupContentsWithID(firstPtrs[i]);
				if(i == priorIndex){
					hash = queue.hash;
					newTerm.ID = queue.ID;
				}
				if(ContentHash.compareRange(queue.hash, hashFrom, hashTo)) newTerm.contents[i] = queue.value;
				if(newTerm.contents[i] == null) newTerm.contents[i] = new String();
				nextPtrs[i] = memory.lookupNextContentPointer(firstPtrs[i]);
				if(priorIndex == i) supplementPtrs = memory.lookupPureContentPointer(firstPtrs[i]);
			}

			if(supplementPtrs.length != 0){
				for(i = 0; i < contentInfo.getAppendixNum(); i++){
					newTerm.appendices[i] = memory.lookupPureContent(supplementPtrs[i]);
				}
			}
			if(ContentHash.compareRange(hash, hashFrom, hashTo)) terms.add(newTerm);
		}

		return terms;
	}

	public int getCounter(){
		return indexcounter;
	}

	public int getTerms(){
		return termcounter;
	}

	public int getSearchcounter(){
		return searchcounter;
	}

	public void getMemoryList(){
		memory.showMemoryElapsed();
	}

	public void initializeMemory(){
		try{
			memory.readMemory();
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	public void saveMemory(){
		memory.saveMemories();
	}

	public int getSkipPointerLen(){
		return memory.getSkipPointerLength();
	}

	private ExtendTerm lookupTerm(String query, int valueType){
		searchcounter++;
		// スキップポインタを見る
		int skipPointerLength = memory.getSkipPointerLength();
		LookupPointer dicPtr = null;
		LookupPointer nextPtr = null;
		ExtendTerm storedTerm = null;
		ExtendTerm returnTerm = null;
		int i;
		int skipLevel = 0;
		int skipVal = 1;
		if(skipPointerLength > 1){
			skipLevel = (int)(Math.log(skipPointerLength - 1) / Math.log(DIGLENGTH));
			skipVal = (int)Math.pow(DIGLENGTH, skipLevel);
		}
		for(i = 0; i < skipPointerLength; i += skipVal){
			dicPtr = memory.lookupSkipPointer(i);
			// System.out.println("Got " + dicPtr);
			// ポインタなし
			if(dicPtr == null) break;

			storedTerm = memory.lookupDictionary(dicPtr, valueType);
			storedTerm.skipptr_index = i;
			int comp = storedTerm.term.compareTo(query);
			if(comp > 0){
				// storeしてるほうが上 -> これより前
				skipLevel--;
				if(skipLevel == -1 || i == 0){
					i--;
					break;
				}
				i -= skipVal;
				skipVal = (int)Math.pow(DIGLENGTH, skipLevel);

			}else if(comp == 0){
				// まさにこの単語
				return storedTerm;
			}

			// 後ろを見る
			if(i + skipVal >= skipPointerLength){
				do{
					skipLevel--;
					if(skipLevel == -1){
						skipVal = 1;
						break;
					}
					skipVal = (int)Math.pow(DIGLENGTH, skipLevel);
				}while(i + skipVal >= skipPointerLength);
			}
		}
		if(i == -1){
			// 一番先頭！ -> storedTermに意味はなくなる
			// 次の単語
			nextPtr = storedTerm.ptr;
			storedTerm = null;
		}else{
			// もう一度スキップポインタ取り出し
			if(i == skipPointerLength) i--;
			dicPtr = memory.lookupSkipPointer(i);
			storedTerm = memory.lookupDictionary(dicPtr, valueType);
		}

		// 辞書順検索
		if(storedTerm != null){
			LookupPointer prevPtr = storedTerm.ptr;
			ExtendTerm nextTerm = memory.lookupDictionary(storedTerm.next, valueType);
			int count = 0;
			while(nextTerm != null){
				int comp = nextTerm.term.compareTo(query);
				if(comp > 0){
					// nextTermが上 -> 辞書になかった
					boolean addSkipPtr = count >= DIGLENGTH ? true : false;
					returnTerm = new ExtendTerm(query, null, prevPtr, nextTerm.ptr, i, addSkipPtr);
					break;
				}else if(comp == 0){
					// ↓はいらないかも
					nextTerm.prev = prevPtr;
					return nextTerm;
				}else{
					prevPtr = nextTerm.ptr;
					nextTerm = memory.lookupDictionary(nextTerm.next, valueType);
				}
				count++;
			}

			if(returnTerm == null){
				// 最後の単語だった
				boolean addSkipPtr = count >= DIGLENGTH ? true : false;
				returnTerm = new ExtendTerm(query, null, prevPtr, null, i, addSkipPtr);
			}
		}else{
			// 一番先頭(i = -1) or 最初の単語(i = 0)
			returnTerm = new ExtendTerm(query, null, null, nextPtr, i, false);
		}

		return returnTerm;
	}

	private void makeSkipPtrForTerm(ExtendTerm term, int type){
		int index = term.skipptr_index;

		if(index == -1){
			// 最初の部分書き換え
			memory.storeSkipPointer(0, type, term.ptr, true, MEM_SKIPPTR);
			refreshSkipPtrDict();
		}else{
			if(term.needSkipPtr){
				// 追加
				memory.storeSkipPointer(index + 1, type, term.ptr, false, MEM_SKIPPTR);
			}
		}
	}

	private void makeSkipPtrForContent(ContentIDs term, int type){
		int index = term.skipPtrIndex;

		if(index == -1){
			// 最初の部分書き換え
			memory.storeSkipPointer(0, type, term.currentPtr, true, MEM_SKIPCONT);
			refreshSkipPtrContent(type);
		}else{
			if(term.addSkipPtr){
				// 追加
				memory.storeSkipPointer(index + 1, type, term.currentPtr, false, MEM_SKIPCONT);
			}
		}
	}

	// 1番目のスキップポインタに固まらない対策
	private void refreshSkipPtrDict(){
		LookupPointer threshold = memory.lookupSkipPointer(1);
		LookupPointer nextPtr = memory.lookupSkipPointer(0);
		LookupPointer prevPtr = null;
		int count = 0;
		if(threshold == null){
			while(nextPtr != null){
				prevPtr = nextPtr;
				nextPtr = memory.lookupDictionaryNext(nextPtr);
				count++;
			}
		}else{
			while(!threshold.equals(nextPtr)){
				prevPtr = nextPtr;
				nextPtr = memory.lookupDictionaryNext(nextPtr);
				count++;
			}
		}

		if(count >= DIGLENGTH){
			memory.storeSkipPointer(1, 0, prevPtr, false, MEM_SKIPPTR);
		}
	}

	// 1番目のスキップポインタに固まらない対策
	private void refreshSkipPtrContent(int type){
		LookupPointer threshold = memory.lookupContentsSkipPointer(1, type);
		LookupPointer nextPtr = memory.lookupContentsSkipPointer(0, type);
		LookupPointer prevPtr = null;
		int count = 0;
		if(threshold == null){
			while(nextPtr != null){
				prevPtr = nextPtr;
				nextPtr = memory.lookupNextContentPointer(nextPtr);
				count++;
			}
		}else{
			while(!threshold.equals(nextPtr)){
				prevPtr = nextPtr;
				nextPtr = memory.lookupNextContentPointer(nextPtr);
				count++;
			}
		}

		if(count >= DIGLENGTH){
//			System.out.println("Count: " + count);
			memory.storeSkipPointer(1, type, prevPtr, false, MEM_SKIPCONT);
		}
	}

	// 辞書をストア
	private ExtendTerm storeDictionary(String query, int valueType) throws UnsupportedEncodingException{
		ExtendTerm newTerm = lookupTerm(query, valueType);

		if(newTerm.ptr == null){
			// 単語追加
			LookupPointer newTermPtr = memory.storeDictionary(query, newTerm.next);
			newTerm.ptr = newTermPtr;

			// ポインタ接続
			if(newTerm.prev != null) memory.storeDictionaryPointer(newTerm.prev, newTermPtr);

			// スキップポインタ追加
			makeSkipPtrForTerm(newTerm, MEM_SKIPPTR);

			this.termcounter++;
		}else{
			// 文章数インクリメント
			memory.incrementDocFreq(newTerm.ptr);
		}

		return newTerm;
	}

	// 本文ポインタ取り出し
	public LinkedHashMap<Long, Integer> getContentsByHash(LookupPointer ptr, int length){
		LinkedHashMap<Long, Integer> indexMap = new LinkedHashMap<Long, Integer>(length);
		LookupPointer currentPtr = ptr;
		LookupPointer lastPtr = null;
		InvertIndexData hash;
		long firstID = ptr.firstID;
		long lastDiff = 0;
		long tmpHash = 0;

		if(ptr.equals(Constants.EMPTY_PTR) || firstID == -1){ return null; }

		for(int i = 0; i < length && currentPtr != null; i++){
			hash = memory.lookupHashID(currentPtr);
			tmpHash = firstID - lastDiff;
			lastDiff += hash.getID();
			indexMap.put(tmpHash, hash.getTermfreq());

			lastPtr = currentPtr;
			currentPtr = memory.lookupNextInvIndex(lastPtr);
		}

		// <!> ptrの中身を破壊的に変更
		if(currentPtr != null){
			ptr.block = lastPtr.block;
			ptr.point = lastPtr.point;
			ptr.firstID = tmpHash;
		}else{
			ptr.block = -1;
			ptr.point = -1;
			ptr.firstID = -1;
		}

		return indexMap;
	}

	private void storeInvIndex(LookupPointer dicPtr, long ID, int termfreq, int valueType){
		// 転置インデックスのポインタ取得
		LookupPointer prevPtr = memory.lookupDictionaryInvIndex(dicPtr, valueType);

		// ID差分計算
		long firstID = memory.lookupDictionaryFirstID(dicPtr, valueType);
		long diff;
		if(firstID == -1L)
			diff = 0L;
		else diff = ID - firstID;

		// 転置インデックス書き込み
		LookupPointer invIndexPtr = memory.storeInvertIndex(prevPtr, diff, termfreq);

		// 辞書ポインタ更新
		memory.storeDictionaryToInvIndex(dicPtr, invIndexPtr, ID, valueType);
	}

	// 本文を探すことになります
	private ContentIDs lookupContentPointer(long ID, int valueType){
		searchcounter++;
		// スキップポインタを見る
		int skipPointerLength = memory.getSkipContentsLength(valueType);
		long nextID = 0;
		LookupPointer nextPtr = null;
		LookupPointer prevPtr = null;
		int i = 0;
		int skipLevel = 0;
		int skipVal = 1;
		if(skipPointerLength > 1){
			skipLevel = (int)(Math.log(skipPointerLength - 1) / Math.log(DIGLENGTH));
			skipVal = (int)Math.pow(DIGLENGTH, skipLevel);
		}
		for(i = 0; i < skipPointerLength; i += skipVal){
			nextPtr = memory.lookupContentsSkipPointer(i, valueType);
//			System.out.println("Skip ptr " + i + ": " + nextPtr);
			// ポインタなし
			if(nextPtr == null) break;

			nextID = memory.lookupContentID(nextPtr);
			if(nextID > ID){
				// 自分より前
				skipLevel--;
				if(skipLevel == -1 || i == 0){
					i--;
					break;
				}
				i -= skipVal;
				skipVal = (int)Math.pow(DIGLENGTH, skipLevel);

			}else if(nextID == ID){
				// まさにこの文章
				return new ContentIDs(ID, nextPtr, null, null, false, i);

			}

			// 後ろを見る
			if(i + skipVal >= skipPointerLength){
				do{
					skipLevel--;
					if(skipLevel == -1){
						skipVal = 1;
						break;
					}
					skipVal = (int)Math.pow(DIGLENGTH, skipLevel);
				}while(i + skipVal >= skipPointerLength);
			}
		}

		if(i == -1){
			// 一番先頭！
			return new ContentIDs(ID, null, null, memory.lookupContentsSkipPointer(0, valueType), true, -1);
		}else{
			// もう一度スキップポインタ取り出し
			if(i == skipPointerLength) i--;
			nextPtr = memory.lookupContentsSkipPointer(i, valueType);
		}

		// 辞書順検索
		int count = 0;

		prevPtr = nextPtr;
		while(nextPtr != null){
			long l = memory.lookupContentID(nextPtr);
			if(l > ID){
				// nextTermが上 -> 辞書になかった
				return new ContentIDs(-1, null, prevPtr, nextPtr, (count >= DIGLENGTH), i);
			}else if(l == ID){
				// IDが一致した
				return new ContentIDs(l, nextPtr, prevPtr, null, false, i);
			}else{
				prevPtr = nextPtr;
				nextPtr = memory.lookupNextContentPointer(nextPtr);
			}
			count++;
		}

		return new ContentIDs(ID, null, prevPtr, nextPtr, (count >= DIGLENGTH), i);
	}

	// 本文をメモリにストア
	public void storeContent(String content, long ID, int index, LookupPointer[] supp){
		ContentIDs contTerm = lookupContentPointer(ID, index);

		// IDがかぶったということは→ 書き換えないで破棄
		if(contTerm.currentPtr == null){
			LookupPointer storePtr = memory.storeContents(content, ID, index, contTerm.nextPtr, supp);
			contTerm.currentPtr = storePtr;

			// 前からポインタ接続
			if(contTerm.prevPtr != null) memory.storeContentNext(contTerm.prevPtr, storePtr);

			// スキップポインタ追加
			makeSkipPtrForContent(contTerm, index);
		}
		indexcounter++;
	}

	// 本文をメモリにストア
	public LookupPointer storePureContent(String content, long ID, int index){
		// IDがかぶったということは→ 書き換えないで破棄
		return memory.storePureContent(content, index);
	}

	// 削除DocIDを追加
	public void deleteContent(long ID, int index){
		ContentIDs infos = lookupContentPointer(ID, index);
		if(infos.currentPtr != null) memory.deleteContents(infos.currentPtr);
	}
}
